﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    /// <summary>
    /// Returns generator for Dictionary to Dictionary mappings.
    /// </summary>
    public static partial class Generators
    {
        public static MappingExpressionGenerator ConcurrentCollectionToConcurrentCollection()
        {
            return (fromExpr, to, customMapping, referenceDictionary) =>
                   (IsEnumerable(fromExpr.Type) && IsSpecialCollection(to))
                       ? customMapping ?? GenerateConcurrentCollectionConversionExpression(fromExpr, to, referenceDictionary)
                       : null;
        }

        private static Expression GenerateConcurrentCollectionConversionExpression(Expression fromExpr, Type to, ParameterExpression referenceDictionary)
        {
            Debug.WriteLine("\tCollectionToConcurrentCollection ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                                                 to.Name);

            Type fromElementType = GetCollectionElementType(fromExpr.Type);
            Type toElementType = GetCollectionElementType(to);

            MethodInfo convertAllMethod = typeof(Generators)
                .GetMethod("Convert" + to.GetGenericTypeName(), BindingFlags.Static | BindingFlags.Public)
                .MakeGenericMethod(fromElementType, toElementType);

            return Expression.Call(convertAllMethod, fromExpr, referenceDictionary);
        }

        #region IsSpecialCollection

        private static bool IsSpecialCollection(Type t)
        {
            if (IsBlockingCollection(t) ||
                IsConcurrentQueue(t) ||
                IsObservableCollection(t) ||
                IsReadOnlyCollection(t) ||
                IsReadOnlyObservableCollection(t))
                return true;

            return false;
        }

       

        private static bool IsConcurrentQueue(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ConcurrentQueue<>);
        }


        private static bool IsBlockingCollection(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BlockingCollection<>);
        }

        private static bool IsObservableCollection(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ObservableCollection<>);
        }

        private static bool IsReadOnlyObservableCollection(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ReadOnlyObservableCollection<>);
        }

        private static bool IsReadOnlyCollection(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>);
        }

        #endregion IsSpecialCollection

        public static BlockingCollection<TOutput> ConvertBlockingCollection<TInput, TOutput>(IEnumerable<TInput> collection
            , ReferenceDictionary  referenceDictionary )
        {
            if (collection == null)
                return null;

            BlockingCollection<TOutput> result = new BlockingCollection<TOutput>();
            foreach (var item in collection)
            {
                result.Add(Mapper.Map<TInput, TOutput>(item, referenceDictionary));
            }
            return result;
        }

        public static ConcurrentQueue<TOutput> ConvertConcurrentQueue<TInput, TOutput>(IEnumerable<TInput> collection
                                                                                           , ReferenceDictionary referenceDictonary)
        {
            if (collection == null)
                return null;
            return new ConcurrentQueue<TOutput>(ConvertEnumerable<TInput, TOutput>(collection, referenceDictonary));
        }

        public static ObservableCollection<TOutput> ConvertObservableCollection<TInput, TOutput>(IEnumerable<TInput> collection
                                                                                          , ReferenceDictionary  referenceDictionary)
        {
            if (collection == null)
                return null;
            return new ObservableCollection<TOutput>(ConvertEnumerable<TInput, TOutput>(collection,  referenceDictionary));
        }

        public static ReadOnlyObservableCollection<TOutput> ConvertReadOnlyObservableCollection<TInput, TOutput>(IEnumerable<TInput> collection
                                                                                           , ReferenceDictionary  referenceDictionary)
        {
            if (collection == null)
                return null;
            return new ReadOnlyObservableCollection<TOutput>(
                new ObservableCollection<TOutput>(ConvertEnumerable<TInput, TOutput>(collection,  referenceDictionary)));
        }

        public static ReadOnlyCollection<TOutput> ConvertReadOnlyCollection<TInput, TOutput>(IEnumerable<TInput> collection
                                                                                           , ReferenceDictionary  referenceDictionary)
        {
            if (collection == null)
                return null;
            return new ReadOnlyCollection<TOutput>(new List<TOutput>(ConvertEnumerable<TInput, TOutput>(collection,  referenceDictionary)));
        }
    }
}
